#include <iostream>
#include <vector>
#include <stdlib.h>
#include <stdio.h>
#include <FreeImage.h>
#include <fstream>
#include <string>
#include <map>
#include <iterator>
#include <cmath>
#include "binFile.h"

struct WB
{
    std::string withe;
    std::string black;

};
std::map<int,WB> ccitt;

void loadCCfile(std::string file)
{
    char key[256];
    std::string w;
    std::string b;
    std::string line;
    WB ccdane;
    std::ifstream ccfile(file.c_str());

    if (ccfile.is_open())
    {
        //std::getline (ccfile,line);
        while (ccfile.good())
        {
            ccfile>>key>>w>>b;
            ccdane.withe=w;
            ccdane.black=b;
            ccitt[atoi(key)]=ccdane;
        }
        ccfile.close();
    }
}

void Eline(int size, std::vector<std::string> *output,int color)
{
    std::string bits="";

    std::map<int,WB>::iterator it2;

    if(size<64)
    {
        bits+=color%2?ccitt[size].black:ccitt[size].withe;
        output->push_back(bits);
    }else if(size==100000000)
    {
        output->push_back("000000000001");
    }
    else
    {
        int length;
        length=size;
        while(length>=64)
        {
            int tmp;
            for(it2 = ccitt.begin(); it2 != ccitt.end(); ++it2)
            {
                if((*it2).first <= length)
                {
                    tmp=(*it2).first;
                }
            }
            length-=tmp;
            bits+=color%2?ccitt[tmp].black:ccitt[tmp].withe;
        }

        bits+=color%2?ccitt[length].black:ccitt[length].withe;

        output->push_back(bits);
    }

}

void encodeLineMode1(FIBITMAP* bitmap, int line, std::vector<std::string> *output)
{
    BYTE byte1;
    BYTE byte2;
    std::vector<int> colorLength;

    unsigned int width = FreeImage_GetWidth(bitmap);

    unsigned int count=1;

    for(int j=0; j<width-1; ++j)
    {
        FreeImage_GetPixelIndex(bitmap,j,line,&byte1);
        FreeImage_GetPixelIndex(bitmap,j+1,line,&byte2);
        if(byte1==byte2)
        {
            ++count;
        }else
        {
            colorLength.push_back(count);
            count=1;
        }
    }
    colorLength.push_back(count);
    colorLength.push_back(100000000);

    for(int i=0; i<colorLength.size();++i)
    {
        Eline(colorLength[i],output,i);
    }
}

int posAB(FIBITMAP* bitmap,int line, int pos)
{
    BYTE color;
    int i=pos;

    unsigned int width = FreeImage_GetWidth(bitmap);

    FreeImage_GetPixelIndex(bitmap,pos,line,&color);

    BYTE color2=color;
    while(color==color2)
    {
        ++i;
        FreeImage_GetPixelIndex(bitmap,i,line,&color2);
        if(i>=width)
        {
            return width;
        }

    }
    return i;
}

int posB(FIBITMAP* bitmap,int line, int a0)
{
    BYTE color;
    BYTE color2;
    BYTE color1;
    BYTE color3;
    unsigned int width = FreeImage_GetWidth(bitmap);
    int pos=width;
    if(a0==-1&&a0==0)
    {
        a0=0;
        color=1;
    }
    else
    {
        FreeImage_GetPixelIndex(bitmap,a0,line,&color);
    }
    for(int i=width-1;i>a0;--i)
    {
        FreeImage_GetPixelIndex(bitmap,i,line-1,&color2);
        FreeImage_GetPixelIndex(bitmap,i-1,line-1,&color1);
        FreeImage_GetPixelIndex(bitmap,i+1,line-1,&color3);

        if(color!=color2 && color1!=color2 && color3==color2)
        {
            pos=i;
        }
    }
    return pos;
}

void encodeLineMode2(FIBITMAP* bitmap,int line,std::vector<std::string> *output)
{
    unsigned int width = FreeImage_GetWidth(bitmap);
    unsigned int height = FreeImage_GetHeight(bitmap);
    int a0 = 0;
    int a1 = 0;
    int a2 = 0;
    int b1 = 0;
    int b2 = 0;

    while(a0 < width)
    {
        a1 = posAB(bitmap,line,a0);
        b1 = posB(bitmap, line, a0);
        b2 = posAB(bitmap,line-1,b1);

        if(b2 < a1)
        {
            output->push_back("0001");
            a0 = b2;
        }else if(std::abs(a1 - b1) <= 3)
        {
            int vertical=a1 - b1;
            switch(vertical)
            {
            case 0:
                output->push_back("1");
                break;
            case 1:
                output->push_back("011");
                break;
            case 2:
                output->push_back("000011");
                break;
            case 3:
                output->push_back("0000011");
                break;
            case -1:
                output->push_back("010");
                break;
            case -2:
                output->push_back("000010");
                break;
            case -3:
                output->push_back("0000010");
                break;
            }
            a0 = a1;
        }else if(std::abs(a1 - b1) > 3)
        {
            a2 = posAB(bitmap,line,a1);
            BYTE color;

            output->push_back("001");
            FreeImage_GetPixelIndex(bitmap,a0,line,&color);
            Eline(a1-a0,output,color+1);

            FreeImage_GetPixelIndex(bitmap,a1,line,&color);
            Eline(a2-a1,output,color+1);

            a0 = a2;
        }
    }

    output->push_back("000000000001");
}


void decode1(std::vector<std::string> output, std::map<int,WB> map,std::vector<int> *decode,bool color)
{
    std::map<int,WB>::iterator it;
    int key=0;
    int tmp=-1;
    int index;
    std::string patern="";
    for(int i=0; i<output.size(); ++i)
    {
        for(int s=0; s<output[i].size(); ++s)
        {
            patern+=output[i][s];

            for(it=map.begin(); it!=map.end();++it)
            {
                index=(*it).first;
                if(map[index].black.compare(patern)==0&&color==0)
                {
                    color=0;
                    tmp=index;
                    patern="";
                }else if(map[index].withe.compare(patern)==0&&color==1)
                {
                    color=1;
                    tmp=index;
                    patern="";
                }else if(patern.compare("000000000001")==0)
                {
                    decode->push_back(-1);
                    return;
                }
            }
            if(tmp>63)
            {
                key+=tmp;
                tmp=-1;
            }else if(tmp!=-1&&tmp<=63)
            {
                key+=tmp;
                decode->push_back(key);
                key=0;
                tmp=-1;
                color=!color;
            }
        }
    }
}

int decode2(FIBITMAP* bitmap,std::vector<std::string> output, std::map<int,WB> map,int width, int line,int offset)
{
    int pos=0;
    bool withe=1;
    int i=offset;

    while(1)
    {
        if(output[i].compare("0001")==0)//pass
        {

            int b1=width;

            BYTE color,color1,color2,color3;

            if(withe)
                color=1;
            else
                color=0;

            for(int j=width-1;j>pos;--j)
            {
                FreeImage_GetPixelIndex(bitmap,j,line-1,&color2);
                FreeImage_GetPixelIndex(bitmap,j-1,line-1,&color1);
                FreeImage_GetPixelIndex(bitmap,j+1,line-1,&color3);

                if(color!=color2 && color1!=color2 && color3==color2)
                {
                    b1=j;
                }
            }
            int b2 = posAB(bitmap,line-1,b1);
            int length=b2 - pos;
            for(int j=pos; j<pos+length;++j)
            {
                FreeImage_SetPixelIndex(bitmap,j,line,&color);
            }
            pos=b2;
            ++i;
        }else if(output[i].compare("001")==0)//horizontal
        {
            std::vector<std::string> v1;
            std::vector<std::string> v2;
            std::vector<int> dec1;
            std::vector<int> dec2;
            v1.push_back(output[i+1]);
            v2.push_back(output[i+2]);

            BYTE color;
            if(withe)
                color=1;
            else
                color=0;


            decode1(v1,ccitt,&dec1,withe);

            for(int j=0; j<dec1[0];++j)
            {
                FreeImage_SetPixelIndex(bitmap,pos,line,&color);
                ++pos;
            }

            withe=!withe;

            if(withe)
                color=1;
            else
                color=0;


            decode1(v2,ccitt,&dec2,withe);

            for(int j=0; j<dec2[0];++j)
            {
                FreeImage_SetPixelIndex(bitmap,pos,line,&color);
                ++pos;
            }
            withe=!withe;
            i+=3;
        }else if(output[i].compare("000000000001")==0)
        {

            pos=0;
            return i+1;
        }
        else//vertical
        {
            int b1=width;

            BYTE color,color1,color2,color3;
            if(withe)
                color=1;
            else
                color=0;

            for(int j=width-1;j>pos;--j)
            {
                FreeImage_GetPixelIndex(bitmap,j,line-1,&color2);
                FreeImage_GetPixelIndex(bitmap,j-1,line-1,&color1);
                FreeImage_GetPixelIndex(bitmap,j+1,line-1,&color3);

                if(color!=color2 && color1!=color2 && color3==color2)
                {
                    b1=j;
                }
            }

            int mode;
            if(output[i].compare("1")==0)
            {
                mode=0;
            }else if(output[i].compare("011")==0)
            {
                mode=1;
            }else if(output[i].compare("000011")==0)
            {
                mode=2;
            }else if(output[i].compare("0000011")==0)
            {
                mode=3;
            }else if(output[i].compare("010")==0)
            {
                mode=-1;
            }else if(output[i].compare("000010")==0)
            {
                mode=-2;
            }else if(output[i].compare("0000010")==0)
            {
                mode=-3;
            }
            if(pos < 0) {
                pos = 0;
            }
            int length=b1+mode-pos;

            for(int j=0; j<length;++j)
            {
                FreeImage_SetPixelIndex(bitmap,pos,line,&color);
                ++pos;
            }

            ++i;
            withe=!withe;
        }
    }
    return 0;
}

void decompress(std::vector<std::string> output,std::string file, int height, int width)
{

    std::vector<int> decode;
    FIBITMAP *bitmapOut = FreeImage_Allocate(width, height, 1);

    RGBQUAD* palette = FreeImage_GetPalette(bitmapOut);
    palette[0].rgbRed = 0;
    palette[0].rgbGreen = 0;
    palette[0].rgbBlue = 0;
    palette[1].rgbRed = 255;
    palette[1].rgbGreen = 255;
    palette[1].rgbBlue = 255;

    decode1(output,ccitt,&decode,1);
    int k=0;
    int offset=0;

    while(decode[k]!=-1)
    {
        for(int j=0; j<decode[k];++j)
        {
            BYTE color;
            k%2?color=0:color=1;
            FreeImage_SetPixelIndex(bitmapOut,offset+j,0,&color);
        }
        offset+=decode[k];
        ++k;
    }

    int o=decode.size();
    for(int i=1; i<height;++i)
    {
        o=decode2(bitmapOut,output,ccitt,width,i,o);

    }

    if(FreeImage_Save(FIF_PNG, bitmapOut,file.c_str()))
    {
        //std::cout<<"DONE"<<std::endl;
    }

}

int main(int argc, char *argv[])
{

    if(argc!=2)
    {
        std::cout<<"bledne paramery"<<std::endl;
        exit(0);
    }


    std::string fileIn(argv[1]);
    std::string fileOut="decompress.png";//(argv[2]);

    std::vector<std::string> output;
    FIBITMAP* bitmap = FreeImage_Load(FIF_PNG, fileIn.c_str());

    FIBITMAP* monochromeImg = FreeImage_Threshold(bitmap,128);
    FreeImage_Unload(bitmap);
    bitmap  = monochromeImg;

    loadCCfile("CCITT.txt");
    unsigned int height = FreeImage_GetHeight(bitmap);
    unsigned int width = FreeImage_GetWidth(bitmap);

    std::cout<<"Encode"<<std::endl;
    encodeLineMode1(bitmap,0,&output);

    for(int i=1; i<height; ++i)
    {
        encodeLineMode2(bitmap,i,&output);

    }
    std::cout<<"End Encode"<<std::endl;

    saveBits(output,"compress.bin");

    std::cout<<"Decompres"<<std::endl;
    decompress(output,fileOut, height,width);
    std::cout<<"End Decompres"<<std::endl;
    return 0;
}
